package org.dftproject.gedcom.ui.pages;

import java.util.Comparator;

import org.dftproject.gedcom.core.Gedcom;
import org.dftproject.gedcom.core.GedcomManager;
import org.dftproject.gedcom.ui.Activator;
import org.dftproject.genesis.data.genealogy.GenealogyConstants;
import org.dftproject.genesis.data.genealogy.GenealogyUtils;
import org.dftproject.genesis.data.genealogy.IName;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.genesis.data.genealogy.Sex;
import org.dftproject.genesis.ui.SharedImages;
import org.dftproject.genesis.ui.pages.AbstractPagePart;
import org.dftproject.genesis.ui.pages.IPageInput;
import org.dftproject.genesis.ui.pages.IPageSite;
import org.dftproject.genesis.ui.pages.PageInitException;
import org.dftproject.genesis.ui.pages.PageManager;
import org.dftproject.genesis.ui.pages.summary.SummaryPage;
import org.dftproject.genesis.ui.pages.summary.SummaryPageInput;
import org.dftproject.genesis.ui.widgets.gallery.Gallery;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IFilter;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.deferred.DeferredContentProvider;
import org.eclipse.jface.viewers.deferred.SetModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import com.eekbom.utils.Strings;

public class GedcomPage extends AbstractPagePart {

	public static final String ID = GedcomPage.class.getName();

	private Text filterText;
	private Table table;
	private TableViewer tableViewer;
	
	private DeferredContentProvider contentProvider;
	
	private String previousFilterText;

	public void init(IPageSite site, IPageInput input) throws PageInitException {
		if (!(input instanceof GedcomPageInput))
			throw new PageInitException("input must be a " + GedcomPageInput.class.getName());
		setInput(input);
		setSite(site);
	}

	@Override
	public void createPartControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.horizontalSpacing = 0;
		layout.numColumns = 2;
		container.setLayout(layout);

		createGallery(container);

		Composite body = new Composite(container, SWT.NONE);
		layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.verticalSpacing = 0;
		layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.verticalSpacing = 5;
		body.setLayout(layout);
		body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		createFilterText(body);

		table = new Table(body, SWT.VIRTUAL | SWT.BORDER);
		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		tableViewer = new TableViewer(table);
		tableViewer.setUseHashlookup(true);
		
		contentProvider = new DeferredContentProvider(new Comparator<IPerson>() {
			
			public int compare(IPerson p1, IPerson p2) {
				return Strings.compareNatural(getName(p1), getName(p2));
			}

			protected String getName(IPerson person) {
				IName name = (IName) GenealogyUtils.getValue(person, GenealogyConstants.name, IName.class);
				if (name != null)
					return GenealogyUtils.stringFromName(name);
				return "";
			}
			
		});
		
		tableViewer.setContentProvider(contentProvider);
		tableViewer.setLabelProvider(new ITableLabelProvider() {

			public Image getColumnImage(Object element, int columnIndex) {
				if (element instanceof IPerson) {
					Sex sex = (Sex) GenealogyUtils.getValue((IPerson) element, GenealogyConstants.sex, Sex.class);
					if (sex == Sex.Male)
						return SharedImages.getDefault().getImage(SharedImages.IMG_MALE);
					else if (sex == Sex.Female)
						return SharedImages.getDefault().getImage(SharedImages.IMG_FEMALE);
				}

				return null;
			}

			public String getColumnText(Object element, int columnIndex) {
				if (element instanceof IPerson) {
					IName name = (IName) GenealogyUtils.getValue((IPerson) element, GenealogyConstants.name, IName.class);
					if (name != null)
						return GenealogyUtils.stringFromName(name);
				}

				return "(unknown)";
			}

			public void dispose() {
			}

			public boolean isLabelProperty(Object element, String property) {
				return false;
			}

			public void addListener(ILabelProviderListener listener) {
			}

			public void removeListener(ILabelProviderListener listener) {
			}

		});
		tableViewer.addDoubleClickListener(new IDoubleClickListener() {

			public void doubleClick(DoubleClickEvent event) {
				ISelection selection = event.getSelection();
				if (selection instanceof IStructuredSelection) {
					Object element = ((IStructuredSelection) selection).getFirstElement();
					if (element instanceof IPerson) {
						IPerson person = ((IPerson) element);

						IWorkbench workbench = PlatformUI.getWorkbench();
						IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
						IWorkbenchPage workbenchPage = window.getActivePage();
						PageManager.getDefault().openPage(workbenchPage, new SummaryPageInput(person), SummaryPage.ID, true, false);
					}
				}
			}

		});
		
		final SetModel model = new SetModel();
		tableViewer.setInput(model);

		Job job = new Job("Load GEDCOM") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
				Gedcom gedcom = GedcomManager.getDefault().getGedcom(((GedcomPageInput) getInput()).getFilename());
				model.addAll(gedcom.getPersons().toArray());
				return Status.OK_STATUS;
			}

		};
		job.schedule();
	}
	
	protected void createGallery(Composite parent) {
		Gallery gallery = new Gallery(parent);
		gallery.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));

		gallery.addItem(Activator.getImageDescriptor("icons/page.png").createImage(), true, "Individuals", null);
		gallery.addItem(Activator.getImageDescriptor("icons/page_white_text.png").createImage(), false, "Source", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new GedcomSourcePageInput(((GedcomPageInput) getInput()).getFilename()), GedcomSourcePage.ID, true, false);
			}

		});
	}

	@Override
	public void setFocus() {
		filterText.setFocus();
	}
	
	protected void textChanged() {
		previousFilterText = getFilterString();
		if (getInitialText().equals(previousFilterText))
			contentProvider.setFilter(null);
		else
			contentProvider.setFilter(new IFilter() {
	
				public boolean select(Object element) {
					if (element instanceof IPerson) {
						IPerson person = (IPerson) element;
						IName name = GenealogyUtils.getName(person);
						if (name != null && GenealogyUtils.stringFromName(name).toUpperCase().contains(previousFilterText.toUpperCase()))
							return true;
					}
					return false;
				}
				
			});
    }
	
	protected String getInitialText() {
		return "type filter text";
	}
	
	protected String getFilterString() {
		return filterText == null ? null : filterText.getText();
	}
	
	protected Control createFilterText(Composite parent) {
		filterText = new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.CANCEL);
		filterText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		filterText.setText(getInitialText());
		
		filterText.addFocusListener(new FocusAdapter() {

			@Override
			public void focusGained(FocusEvent e) {
				/*
				 * Running in an asyncExec because the selectAll() does not
				 * appear to work when using mouse to give focus to text.
				 */
				Display display = filterText.getDisplay();
				display.asyncExec(new Runnable() {

					public void run() {
						if (!filterText.isDisposed()) {
							if (getInitialText().equals(filterText.getText().trim()))
								filterText.selectAll();
						}
					}

				});
			}

		});
		
        filterText.addKeyListener(new KeyAdapter() {

            @Override
			public void keyPressed(KeyEvent e) {
            	// on a CR we want to transfer focus to the list
				boolean hasItems = tableViewer.getTable().getItemCount() > 0;
				if (hasItems && e.keyCode == SWT.ARROW_DOWN) {
					tableViewer.getTable().setFocus();
				} else if (e.character == SWT.CR) {
					return;
				}
            }
            
        });
        
        // enter key set focus to tree
        filterText.addTraverseListener(new TraverseListener () {
        	
			public void keyTraversed(TraverseEvent e) {
				if (e.detail == SWT.TRAVERSE_RETURN) {
					e.doit = false;
					if (tableViewer.getTable().getItemCount() == 0) {
						Display.getCurrent().beep();
					} else {
						tableViewer.getTable().setFocus();
					}
				}
			}
			
		});
        
        filterText.addModifyListener(new ModifyListener() {

        	public void modifyText(ModifyEvent e) {
        		textChanged();
        	}
        	
        });
        
        return filterText;
	}

}
